package io.gitee.horizon007.webspider.metadata;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class MetadataUtils {

    private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
    private static final Map<Class<?>, ClassMetadata> cacheMap = new ConcurrentHashMap<>();

    public static ClassMetadata metadata(Class<?> type) throws IntrospectionException, IllegalAccessException {
        ClassMetadata classMetadata = cacheMap.get(type);
        if (classMetadata != null) {
            return classMetadata;
        }

        List<PropertyMetadata> propertyMetadataList = new ArrayList<>();

        classMetadata = new ClassMetadata(
                type,
                getNoArgConstructor(type),
                Collections.unmodifiableList(propertyMetadataList)
        );

        Map<String, Field> declaredFieldMap = getDeclaredFieldMap(type);
        for (PropertyDescriptor propertyDescriptor : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
            Method writeMethod = propertyDescriptor.getWriteMethod();
            if (writeMethod == null || writeMethod.getDeclaringClass() == Object.class) {
                continue;
            }

            Field field = declaredFieldMap.get(propertyDescriptor.getName());

            String _name = propertyDescriptor.getName();
            Type _genericType = writeMethod.getGenericParameterTypes()[0];
            Class<?> _classType = writeMethod.getParameterTypes()[0];
            MethodHandle _writeMethod = lookup.unreflect(writeMethod);
            Map<Class<? extends Annotation>, Annotation> _annotationMap = new HashMap<>();

            if (field != null) {
                for (Annotation annotation : field.getAnnotations()) {
                    _annotationMap.put(annotation.annotationType(), annotation);
                }
            }

            for (Annotation annotation : writeMethod.getAnnotations()) {
                _annotationMap.put(annotation.annotationType(), annotation);
            }

            propertyMetadataList.add(
                    new PropertyMetadata(
                            classMetadata,
                            _name,
                            _genericType,
                            _classType,
                            _writeMethod,
                            Collections.unmodifiableMap(_annotationMap)
                    )
            );
        }

        cacheMap.put(type, classMetadata);

        return classMetadata;
    }

    private static MethodHandle getNoArgConstructor(Class<?> type) throws IllegalAccessException {
        for (Constructor<?> constructor : type.getConstructors()) {
            if (constructor.getParameterCount() == 0) {
                return lookup.unreflectConstructor(constructor);
            }
        }
        return null;
    }

    private static Map<String, Field> getDeclaredFieldMap(Class<?> type) {
        return Arrays.stream(type.getDeclaredFields())
                     .collect(Collectors.toMap(Field::getName, v -> v));
    }


}
